//select用于监听多个文件描述符

#pragma once
#include <iostream>
#include <string>
#include "Socket.hpp"
#include <memory>
#include "Log.hpp"
#include <sys/select.h>

using namespace SocketModule;
using namespace LogModule;

#define NUM sizeof(fd_set)*8

class SelectServer
{
public:
    SelectServer(int port)
    :_port(port)
    ,_listen_socket(std::make_unique<TcpSocket>())
    ,_isrunning(false)
    {}

    void Init()
    {
        _listen_socket->BuildTcpSocketMethod(_port);
        for(int i=0;i<NUM;i++)
        {
            _fd_array[i]=gdefaultsockfd;
        }
        _fd_array[0]=_listen_socket->Fd();
    }

    void Loop()
    {
        _isrunning=true;
        fd_set rfds;//用于r的fd集合，就是个结构体，里面是个数组，当位图用
        while(_isrunning)
        {
            //fd集合每次输入是要关注的文件描述符，输出是哪些文件就绪了
            FD_ZERO(&rfds);
            //timeout 会被更新为实际剩余的时间（即传入时的剩余时间）。如果 select 是由于等待超时而返回，timeout 会变为 0。
            struct timeval timeout={10,0};//在 select 系统调用中，如果设置了 timeout 等待文件描述符的变化，如果某个文件描述符在 timeout 时间前发生了变化，select 会立即返回，而不是等到指定的 timeout 时间结束。
            int maxfd=gdefaultsockfd;

            for(int i=0;i < NUM;i++)
            {
                if(_fd_array[i] == gdefaultsockfd)
                {
                    continue;
                }
                else
                {
                    FD_SET(_fd_array[i],&rfds);
                    if(_fd_array[i]>maxfd)
                    {
                        maxfd=_fd_array[i];
                    }
                }
            }

            int n=select(maxfd+1,&rfds,nullptr,nullptr,&timeout);
            switch(n)
            {
            case 0:
                std::cout<<"timeout"<<std::endl;
                break;
            case -1:
                std::cout<<"select error"<<std::endl;
                break;
            default:
                std::cout<<"有事件就绪了"<< timeout.tv_sec << ":" << timeout.tv_usec <<std::endl;
                Dispatcher(rfds);
                break;
                

            }
        }

        _isrunning=false;


    }

    void Dispatcher(fd_set &rfds)//将listen套接字文件描述符和其余普通套接字一起交由select监听，再分发
    {
        for(int i=0;i<NUM;i++)
        {
            if(_fd_array[i] == gdefaultsockfd)
            {
                continue;
            }
            else
            {
                if(FD_ISSET(_fd_array[i],&rfds))//listen套接字在就绪名单上
                {
                    if(_fd_array[i] == _listen_socket->Fd())
                    {
                        Accept();
                    }
                    else
                    {
                        Recver(i);//需要传套接字

                    }


                }


            }
        }

    }

    void Accept()
    {
        InetAddr client;
        int newfd= _listen_socket->Accepter(&client);//第一次绝不会阻塞，但是我也只accep一次，第二次可能阻塞，哪怕有更多我也只弄一次

        if(newfd<0)
        {
            return ;
        }
        else
        {
            int i=0;
            for(; i<NUM ;i++)
            {
                if(_fd_array[i]!=gdefaultsockfd)
                {
                    continue;
                }
                else
                {
                    _fd_array[i]=newfd;
                    break;
                }
            }

            if(i==NUM)
            {
                std::cout<<"select满了"<<std::endl;
                ::close(newfd);
            }

        }
    }

    void Recver(int i)
    {
        char buffer[1024];
        ssize_t n=recv(_fd_array[i],buffer,sizeof(buffer)-1,0);
        if(n==0)
        {
            std::cout<<"tcp 断开"<<std::endl;
            close(_fd_array[i]);
            _fd_array[i]=gdefaultsockfd;
        }
        else if(n>0)
        {
            buffer[n]=0;
            std::string s="echo# ";
            s+=buffer;
            ::send(_fd_array[i],s.c_str(),s.size(),0);
        }
        else
        {
            std::cout<<"recv error"<<std::endl;
            close(_fd_array[i]);
            _fd_array[i]=gdefaultsockfd;

        }

    }

private:
    uint16_t _port;
    std::unique_ptr<Socket> _listen_socket;
    bool _isrunning;
    int _fd_array[NUM];//辅助数组，大小为select可以监听文件描述符个数  不用vector这种可记录大小容器是为了
    //体现遍历

};